/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2013-2019.
 * Description: entry for proc hstat module, support topp feature
 * Author: heshaoliang
 * Create: 2013-6-15
 */

#include <linux/module.h>
#include <linux/cpumask.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/irqnr.h>
#include <linux/io.h>
#include <linux/task_struct_extend.h>

#define HSTAT_BUF_SIZE_BASE 4096
#define HSTAT_BUF_SIZE_FACTOR 128
#define HSTAT_BUF_SIZE_THRESHOLD 0x20000 /* 128 * 1024 */
#define MSATIME64_ZERO (0ULL)

DEFINE_PER_CPU(struct kernel_cpustat, msa_kstat);
EXPORT_PER_CPU_SYMBOL(msa_kstat);

static void show_hstat_allcpu(struct seq_file *p)
{
	int i;
	msatime64_t user, nice, system, idle, iowait, irq, softirq, steal;
	msatime64_t guest, guest_nice;

	user = nice = system = idle = iowait =
		irq = softirq = steal = MSATIME64_ZERO;
	guest = guest_nice = MSATIME64_ZERO;

	for_each_possible_cpu(i) {
		user = msatime64_add(user, msa_kstat_cpu(i).cpustat[CPUTIME_USER]);
		nice = msatime64_add(nice, msa_kstat_cpu(i).cpustat[CPUTIME_NICE]);
		system = msatime64_add(system, msa_kstat_cpu(i).cpustat[CPUTIME_SYSTEM]);
		idle = msatime64_add(idle, msa_kstat_cpu(i).cpustat[CPUTIME_IDLE]);
		iowait = msatime64_add(iowait, msa_kstat_cpu(i).cpustat[CPUTIME_IOWAIT]);
		irq = msatime64_add(irq, msa_kstat_cpu(i).cpustat[CPUTIME_IRQ]);
		softirq = msatime64_add(softirq, msa_kstat_cpu(i).cpustat[CPUTIME_SOFTIRQ]);
		steal = msatime64_add(steal, msa_kstat_cpu(i).cpustat[CPUTIME_STEAL]);
		guest = msatime64_add(guest, msa_kstat_cpu(i).cpustat[CPUTIME_GUEST]);
		guest_nice = msatime64_add(guest_nice, msa_kstat_cpu(i).cpustat[CPUTIME_GUEST_NICE]);
	}

	seq_printf(p, "cpu  %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
		(unsigned long long)user,
		(unsigned long long)nice,
		(unsigned long long)system,
		(unsigned long long)idle,
		(unsigned long long)iowait,
		(unsigned long long)irq,
		(unsigned long long)softirq,
		(unsigned long long)steal,
		(unsigned long long)guest,
		(unsigned long long)guest_nice);
}

static void show_hstat_percpu(struct seq_file *p)
{
	int i;
	msatime64_t user, nice, system, idle, iowait, irq, softirq, steal;
	msatime64_t guest, guest_nice;

	for_each_online_cpu(i) {
		/* Copy values here to work around gcc-2.95.3, gcc-2.96 */
		user = msa_kstat_cpu(i).cpustat[CPUTIME_USER];
		nice = msa_kstat_cpu(i).cpustat[CPUTIME_NICE];
		system = msa_kstat_cpu(i).cpustat[CPUTIME_SYSTEM];
		idle = msa_kstat_cpu(i).cpustat[CPUTIME_IDLE];
		iowait = msa_kstat_cpu(i).cpustat[CPUTIME_IOWAIT];
		irq = msa_kstat_cpu(i).cpustat[CPUTIME_IRQ];
		softirq = msa_kstat_cpu(i).cpustat[CPUTIME_SOFTIRQ];
		steal = msa_kstat_cpu(i).cpustat[CPUTIME_STEAL];
		guest = msa_kstat_cpu(i).cpustat[CPUTIME_GUEST];
		guest_nice = msa_kstat_cpu(i).cpustat[CPUTIME_GUEST_NICE];
		seq_printf(p,
			"cpu%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
			i,
			(unsigned long long)user,
			(unsigned long long)nice,
			(unsigned long long)system,
			(unsigned long long)idle,
			(unsigned long long)iowait,
			(unsigned long long)irq,
			(unsigned long long)softirq,
			(unsigned long long)steal,
			(unsigned long long)guest,
			(unsigned long long)guest_nice);
	}
}

static int show_hstat(struct seq_file *p, void *v)
{
	show_hstat_allcpu(p);
	show_hstat_percpu(p);

#if defined(CONFIG_RTOS_MICROSTATE_ACCT_SCHED_CLOCK_CLOCKSOURCE)
	seq_printf(p, "cpu_khz=%llu\n", (unsigned long long)(1000000));
#elif defined(CONFIG_RTOS_MICROSTATE_ACCT_TSC_CLOCKSOURCE)
	seq_printf(p, "cpu_khz=%llu\n", (unsigned long long)cpu_khz);
#endif
	return 0;
}

static int hstat_open(struct inode *inode, struct file *file)
{
	size_t size = HSTAT_BUF_SIZE_BASE + HSTAT_BUF_SIZE_FACTOR * num_possible_cpus();
	if (size > HSTAT_BUF_SIZE_THRESHOLD)
		size = HSTAT_BUF_SIZE_THRESHOLD;

	return single_open_size(file, show_hstat, NULL, size);
}

static const struct proc_ops proc_hstat_operations = {
	.proc_open = hstat_open,
	.proc_read_iter = seq_read_iter,
	.proc_lseek = seq_lseek,
	.proc_release = single_release,
};

static int __init proc_hstat_init(void)
{
	proc_create("hstat", 0444, NULL, &proc_hstat_operations);
	return 0;
}

#ifdef CONFIG_RTOS_MICROSTATE_ACCT
/*
 * provides microstate accounting information
 */
static const char * const statenames[] = {
	"User",
	"System",
	"Interruptible",
	"Uninterruptible",
	"OnRunQueue",
	"Zombie",
	"Stopped",
	"Interrupted",
	"Paging",
	"Futex",
	"Poll"
};

static void write_thread_hstat_total(struct seq_file *m, const struct microstates *msp,
		const msa_time_t *tp, const msa_time_t now)
{
	if (m == NULL) {
		pr_err("topp: seq_file is NULL so unable to print\n");
		return;
	}
	if (msp == NULL || tp == NULL) {
		pr_err("topp: msp or tp is NULL. msp: %p, tp: %p\n", msp, tp);
		return;
	}

	seq_printf(m,
		"State:%s "		\
		"Now: %llu "		\
		"cpu_khz: %llu\n"	\
		"total %llu "		\
		"%llu "			\
		"%llu "			\
		"%llu "			\
		"%llu "			\
		"%llu "			\
		"%llu "			\
		"%llu "			\
		"%llu "			\
		"%llu "			\
		"%llu\n",		\
		msp->cur_state >= 0 && msp->cur_state < MSA_NR_STATES ?
		statenames[msp->cur_state] : "Impossible",
		(unsigned long long)(now),
#if defined(CONFIG_RTOS_MICROSTATE_ACCT_SCHED_CLOCK_CLOCKSOURCE)
		(unsigned long long)(1000000),
#elif defined(CONFIG_RTOS_MICROSTATE_ACCT_TSC_CLOCKSOURCE)
		(unsigned long long)(cpu_khz),
#endif
		(unsigned long long)(tp[MSA_ONCPU_USER]),
		(unsigned long long)(tp[MSA_ONCPU_SYS]),
		(unsigned long long)(tp[MSA_INTERRUPTIBLE_SLEEP]),
		(unsigned long long)(tp[MSA_UNINTERRUPTIBLE_SLEEP]),
		(unsigned long long)(tp[MSA_INTERRUPTED]),
		(unsigned long long)(tp[MSA_ONRUNQUEUE]),
		(unsigned long long)(tp[MSA_STOPPED]),
		(unsigned long long)(tp[MSA_ZOMBIE]),
		(unsigned long long)(tp[MSA_POLL_SLEEP]),
		(unsigned long long)(tp[MSA_PAGING_SLEEP]),
		(unsigned long long)(tp[MSA_FUTEX_SLEEP]));
}

static void msa_adjust_now(const struct microstates *msp1, msa_time_t *now)
{
	if (msa_get_account_enabled() == 1)
		MSA_NOW(*now);
	else
		*now = msp1->last_change;
}

int proc_tid_hstat(struct seq_file *m, struct pid_namespace *ns,
				struct pid *pid, struct task_struct *task)
{
	msa_time_t now;
	msa_time_t *tp;
	struct microstates *msp1;
	struct rtos_task_struct *rtos_task = task_to_rtos_task(task);

	msp1 = (struct microstates *)kmalloc(sizeof(struct microstates), GFP_KERNEL);
	if (msp1 == NULL)
		return -ENOMEM;

	memcpy(msp1, rtos_task->microstates, sizeof(struct microstates));
	/* use to calculate the timers for each thread */
	tp = msp1->timers;

	switch (msp1->cur_state) {
	case MSA_ONCPU_USER:
	case MSA_ONCPU_SYS:
	case MSA_ONRUNQUEUE:
		now = msp1->last_change;
		break;
	default:
		msa_adjust_now(msp1, &now);
		tp[msp1->cur_state] += now - msp1->last_change;
	}
	write_thread_hstat_total(m, msp1, tp, now);
	kfree(msp1);
	return 0;
}
/*
 * Calculate all task's and children hstat.
 */
static void calculate_task_hstat(struct microstates *msp, struct microstates *msp1,
				struct task_struct *task)
{
	/* tp, tp1: use to calculate the total times for the process which has several threads */
	msa_time_t *tp, *tp1;
	struct rtos_task_struct *rtos_task;
	int i = 0;
	read_lock(&tasklist_lock);
	do {
		rtos_task = task_to_rtos_task(task);
		memcpy(msp, rtos_task->microstates, sizeof(struct microstates));
		for (i = 0, tp = msp->timers, tp1 = msp1->timers; i < MSA_NR_STATES; i++)
			*tp1++ += *tp++;
		task = next_thread(task);
	} while (!thread_group_leader(task));
	read_unlock(&tasklist_lock);
}

/*
 * Sum resource usage for self + children.
 * Only a snapshot, don't worry about things changing under foot.
 */
int proc_tgid_hstat(struct seq_file *m, struct pid_namespace *ns,
			  struct pid *pid, struct task_struct *task)
{
	msa_time_t *tp;

	struct microstates *msp, *msp1;
	msa_time_t now;

	msp = (struct microstates *)kzalloc(sizeof(struct microstates) * 2, GFP_KERNEL);
	if (msp == NULL)
		return -ENOMEM;
	msp1 = msp + 1;

	MSA_NOW(now);
	calculate_task_hstat(msp, msp1, task);
	tp = msp1->timers;
	switch (msp->cur_state) {
	case MSA_ONCPU_USER:
	case MSA_ONCPU_SYS:
	case MSA_ONRUNQUEUE:
		now = msp->last_change;
		break;
	default:
		msa_adjust_now(msp, &now);
		tp[msp->cur_state] += now - msp->last_change;
	}
	write_thread_hstat_total(m, msp, tp, now);
	kfree(msp);
	return 0;
}

#ifdef CONFIG_RTOS_THREAD_TIME_HSTAT
/*
 * Quickly reads the utime and stime of a task.
 * This interface is the same as that of proc_tid_hstat,
 * but only utime and stime are obtained.
 */
int task_cputime_hstat_get(struct task_struct *task, msa_time_t *ut, msa_time_t *st)
{
	struct rtos_task_struct *rtos_task = NULL;

	if (!task || !ut || !st)
		return -EINVAL;

	rtos_task = task_to_rtos_task(task);
	*ut = rtos_task->microstates->timers[MSA_ONCPU_USER];
	*st = rtos_task->microstates->timers[MSA_ONCPU_SYS];
	return 0;
}
EXPORT_SYMBOL_GPL(task_cputime_hstat_get);

/*
 * Quickly reads the sum of utimes and the sum of stimes of a thread group
 * This interface is the same as that of proc_tgid_hstat,
 * but only utime and stime are obtained.
 * The proc_tgid_hstat interface does not return cutime and cstime.
 * Therefore, the output parameter directly returns 0.
 *
 * CAUTION: In 5.10 kernel read/write lock mechanism requires that read_lock
 *          cannot be used consecutively in one thread. Therefore, we do not
 *          use read_lock on tasklist_lock in this function, even though we
 *          have to.
 *          The function caller must ensure that tasklist_lock is locked by
 *          read_lock before calling this function and then read_unlock the
 *          tasklist_lock after the call is complete.
 */
int thread_group_cputime_hstat_get(struct task_struct *task, msa_time_t *ut, msa_time_t *st,
				   msa_time_t *cut, msa_time_t *cst)
{
	struct rtos_task_struct *rtos_task = NULL;

	if (!task || !ut || !st || !cut || !cst)
		return -EINVAL;

	*ut = *st = *cut = *cst = 0;
	do {
		rtos_task = task_to_rtos_task(task);
		*ut += rtos_task->microstates->timers[MSA_ONCPU_USER];
		*st += rtos_task->microstates->timers[MSA_ONCPU_SYS];
		task = next_thread(task);
	} while (!thread_group_leader(task));
	return 0;
}
EXPORT_SYMBOL_GPL(thread_group_cputime_hstat_get);
#endif /* CONFIG_RTOS_THREAD_TIME_HSTAT */

#endif /* CONFIG_RTOS_MICROSTATE_ACCT */

module_init(proc_hstat_init);
